/*
* Copyright Nguyen Minh Kha - 07520169
* 11:00 AM 16/09/2013 (hh:mm dd/mm/yyyy) 
* License: Free to use
*/

#include "stdafx.h"
#include "SpriteTexture.h"
#include "SpriteSurface.h"
#include "sound_manager.h"
#include "sound_mp3.h"
#include "control.h"
#include "collision.h"


// forward declaration
BOOL InitInstance(HINSTANCE, int );
ATOM MyRegisterClass(HINSTANCE);
LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);

// Game function

int GameInit(HWND);
void GameRun(HWND);
void GameEnd(HWND);

int LoadGameResource();

// screen function
void FullScreen();
void Windowed();

// move function
void MoveLeft(HWND);
void MoveRight(HWND);

// Global value
HWND globalHWND;
HDC globalHDC;
HINSTANCE globalHInstance;
LPDIRECT3D9 d3d = NULL;
LPDIRECT3DDEVICE9 d3ddev = NULL;
bool isFullScreen = FULLSCREEN;

int globalScreenWidth = 0;
int globalScreenHeight = 0;

DWORD styleFullScreen;
DWORD styleWindowed;

RECT windowRect;

LPDIRECT3DSURFACE9 backBuffer = NULL;
LPDIRECT3DSURFACE9 surface = NULL;
LPD3DXSPRITE spriteHandler = NULL;

bool exiting = false;
DWORD startTime;
int tickForNextFrame = 1000 / FPS;

/* Game object */
SpriteTexture* spriteTexture;
SpriteSurface* spriteSurface;
// Sound
SoundManager* soundManager = NULL;
SoundMp3* backGroundMP3 = NULL;
// Control
Control* controlManager = NULL;

// Colision
Collision* collisionManager = NULL;

// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
	// copy global HINSTANCE
	globalHInstance = hInstance;
	// set style
	styleWindowed = WS_OVERLAPPEDWINDOW;
	styleFullScreen = WS_EX_TOPMOST | WS_VISIBLE | WS_POPUP;


	// collect system infomation
	globalScreenWidth = GetSystemMetrics(SM_CXSCREEN);
	globalScreenHeight = GetSystemMetrics(SM_CYSCREEN);

	//Register class
	MyRegisterClass(hInstance);

	//Initialize application
	if(!InitInstance(hInstance, nCmdShow))
		return FALSE;

	// init game
	if(GameInit(globalHWND) == 0)
	{
		MessageBox(globalHWND, L"Error in game init", L"Error", MB_OK);
	}

	// game loop
	while(TRUE)
	{
		// Sleep(1);
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message == WM_QUIT)
				break;
			// Decode and pass message to WinProc
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		// game stuff
		GameRun(globalHWND);
	}
	// destroy game
	GameEnd(globalHWND);
}

// create new window
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND hWnd;
	DWORD windowStyle = styleWindowed;
	int width = 0;
	int height = 0;
	if(FULLSCREEN)
	{
		windowStyle = styleFullScreen;
		width = globalScreenWidth;
		height = globalScreenHeight;
	}
	else
	{
		width = SCREEN_WIDTH;
		height = SCREEN_HEIGHT;
	}
	hWnd = CreateWindow(
		APPTITLE,
		APPTITLE,
		windowStyle,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		width,
		height,
		NULL,
		NULL,
		hInstance,
		NULL);
	if(!hWnd)
		return FALSE;
	// save hWnd
	globalHWND = hWnd;

	// display window
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

// RegisterClass

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wc;
	wc.cbSize = sizeof(WNDCLASSEX); // create memory

	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WinProc;

	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = NULL;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = APPTITLE;
	wc.hIconSm = NULL;

	return RegisterClassEx(&wc);

}

// Windows event call back function
LRESULT CALLBACK WinProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_DESTROY:
			GameEnd(hWnd);
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

int LoadGameResource()
{
	// for debug

	HRESULT  result;
	// create surface (must work with back buffer when call)
	result = d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
	if(result)
		return 0; // error
	// Get image info
	D3DXIMAGE_INFO info;
	result = D3DXGetImageInfoFromFile(BACKGROUND_FILE_NAME, &info);
	if(result)
		return 0;
	// create surface
	result =  d3ddev->CreateOffscreenPlainSurface(
		info.Width, // width
		info.Height, // height
		D3DFMT_X8R8G8B8,
		D3DPOOL_DEFAULT,
		&surface,
		NULL);
	if(result)
		return 0;

	/* Load all resource */
	result = D3DXLoadSurfaceFromFile(surface, NULL, NULL, BACKGROUND_FILE_NAME, NULL, D3DX_DEFAULT, 0, NULL);
	if(result)
		return 0; // fatal erro

	//return fine
	return 1;
}

int GameInit(HWND hWnd)
{
	// Direct 3D
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if(d3d == NULL)
	{
		MessageBox(hWnd, L"Can not create d3d object", L"Error", MB_OK);
		return 0;
	}

	// set parameter to create device
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	if(FULLSCREEN)
	{
		d3dpp.Windowed = FALSE;
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	
		d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
		d3dpp.BackBufferCount = 1;
		d3dpp.BackBufferWidth = globalScreenWidth;
		d3dpp.BackBufferHeight = globalScreenHeight;
		d3dpp.hDeviceWindow = hWnd;
	}
	else
	{
		d3dpp.Windowed = TRUE;
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	
		d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	}

	// create d3d device
	d3d->CreateDevice(
		D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,
		hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&d3dpp,
		&d3ddev);
	if(d3ddev == NULL)
	{
		MessageBox(hWnd, L"Error create D3D device", L"Error", MB_OK);
		return 0;
	}

	// Create SpriteHandler
	D3DXCreateSprite(d3ddev, &spriteHandler);
	if(spriteHandler == NULL)
	{
		MessageBox(hWnd, L"Error create sprite handler", L"Error", MB_OK);
		return 0;
	}

	// random seed
	srand(time(NULL));

	// Get the start time
	startTime = GetTickCount();

	// Init Game Object

	spriteSurface = new SpriteSurface(TILED_FILE_NAME, d3ddev);

	if(spriteSurface->LoadResource())
	{
		MessageBox(hWnd, L"Error load sprite-surface", L"Error", MB_OK);
		return 0;
	}


	spriteTexture = new SpriteTexture(TILED_FILE_NAME, d3ddev, D3DCOLOR_XRGB(255,255,255));
	if(spriteTexture->LoadResource())
	{
		MessageBox(hWnd, L"Error load sprite-texture", L"Error", MB_OK);
		return 0;
	}

	/* Load sounds */
	soundManager = new SoundManager();
	if(soundManager->Init(globalHWND))
		return 0;
	backGroundMP3 = new SoundMp3(L"background.mp3", soundManager);
	
	//Sleep(50000);
	
	/* Load control */
	controlManager = new Control();
	controlManager->Init(globalHInstance, globalHWND);

	// Load collision
	collisionManager = new Collision();

	// load all resource check if program can start
	return LoadGameResource();
}

void GameEnd(HWND hWnd)
{
	if(d3ddev != NULL)
		d3ddev->Release();
	if(d3d != NULL)
		d3d->Release();
}

void GameRun(HWND hWnd)
{
		

	if(d3ddev == NULL)
		return;
	if(exiting == true)
		return;
	// Streaming and play sound
	backGroundMP3->Play(true);

	// catch key, mouse
	controlManager->CatchEvent();

	// check collision
	if (collisionManager->CheckCollisionRect(spriteSurface, spriteTexture))
	{
		MessageBox(globalHWND, L"Two cat is collide.", L"Collision", MB_OK);
		// reset the run

	}

	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 255, 255), 1.0f, 0);
	if(d3ddev->BeginScene())
	{
		// Load background
		d3ddev->StretchRect(surface, NULL, backBuffer, NULL, D3DTEXF_NONE);


		// check fps threshold
		if(GetTickCount() - startTime >= tickForNextFrame)
		{
			startTime = GetTickCount();

			// auto check and go next frame
			spriteTexture->Next(false);

			spriteSurface->Next(true);
		}

		// get backbuffer rect for sprite
		RECT destRect;
		destRect.left = spriteSurface->spriteInfo.x;
		destRect.top = SPRITE_Y_SURFACE;
		destRect.bottom = destRect.top + spriteSurface->spriteInfo.height * SPRITE_SCALE_FACTOR;
		destRect.right = destRect.left + spriteSurface->spriteInfo.width * SPRITE_SCALE_FACTOR;

		// Render surface-sprite
		d3ddev->StretchRect(
			spriteSurface->GetSurface(), 
			spriteSurface->GetCurrentRect(),
			backBuffer, 
			&destRect,
			D3DTEXF_NONE);

		// Begin render texture sprite
		spriteHandler->Begin(D3DXSPRITE_ALPHABLEND);
		
		D3DXVECTOR3 position((float)spriteTexture->spriteInfo.x , (float)spriteTexture->spriteInfo.y, 0); // new position on tiled texture, surface

		// tranform sprite

		// Texture being used 
		D3DXVECTOR2 spriteCentre = D3DXVECTOR2(spriteTexture->spriteInfo.width, spriteTexture->spriteInfo.height);

		// Screen position of the sprite
		D3DXVECTOR2 trans = D3DXVECTOR2(position.x, position.y);

		// Rotate based on the time passed
		float rotation = 0;

		// Build our matrix to rotate, scale and position our sprite
		D3DXMATRIX mat;

		D3DXVECTOR2 scaling(0.3f,0.3f);

		// out, scaling centre, scaling rotation, scaling, rotation centre, rotation, translation
		D3DXMatrixTransformation2D(&mat,NULL,0.0, &scaling, &spriteCentre, rotation, &trans);
		
		// Tell the sprite about the matrix
		spriteHandler->SetTransform(&mat);
		
		spriteHandler->Draw(
			spriteTexture->GetTexture(),
			spriteTexture->GetCurrentRect(),
			NULL,
			&position,
			D3DCOLOR_XRGB(255, 255, 255));


		// End render sprite
		spriteHandler->End();

		// stop render
		d3ddev->EndScene();
	}
	// Display back buffer to the screen
	d3ddev->Present(NULL, NULL, NULL, NULL);

	// check key press
	if(KEY_DOWN(VK_DOWN))
	{
		exiting = true;
		PostMessage(hWnd, WM_DESTROY, NULL, NULL);
	}

	//// exit full screen
	//if(KEY_DOWN(VK_ESCAPE))
	//{
	//	Windowed();
	//}

	//// full screen
	//if(KEY_DOWN(VK_RETURN))
	//{
	//	// FullScreen();
	//}

	if(KEY_DOWN(VK_LEFT))
	{
		MoveLeft(hWnd);
	}

	if(KEY_DOWN(VK_RIGHT))
	{
		MoveRight(hWnd);
	}
}

void FullScreen()
{
	if(isFullScreen == false)
	{
		// updte window setting
		DWORD style = styleFullScreen;
		SetWindowLong(globalHWND, GWL_STYLE, style);

		// release - MUST RELEASE before reset
		surface->Release();
		backBuffer->Release();

		D3DPRESENT_PARAMETERS d3dpp;
		ZeroMemory(&d3dpp, sizeof(d3dpp));
		d3dpp.Windowed = FALSE;
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	
		d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
		d3dpp.BackBufferCount = 1;
		d3dpp.BackBufferWidth = globalScreenWidth;
		d3dpp.BackBufferHeight = globalScreenHeight;

		// reset device
		HRESULT result = d3ddev->Reset(&d3dpp);
		if( result != S_OK)
		{
			MessageBox(globalHWND, L"Error when become full screen", L"Error", MB_OK);
		}

		// Load resource again
		if(LoadGameResource() == 0)
			MessageBox(globalHWND, L"Error when become full screen", L"Error", MB_OK);
		// set window position
		SetWindowPos(globalHWND, HWND_NOTOPMOST, 0, 0, globalScreenWidth, globalScreenHeight, SWP_SHOWWINDOW);	
		//update window
		UpdateWindow(globalHWND);
		// now it fullscreen
		isFullScreen = true;
	}
}

void Windowed()
{
	if(isFullScreen)
	{
		// updte window setting
		DWORD style = styleWindowed;
		SetWindowLong(globalHWND, GWL_STYLE, style);

		// release - MUST RELEASE before reset
		surface->Release();
		backBuffer->Release();

		// update directx device
		D3DPRESENT_PARAMETERS d3dpp;
		ZeroMemory(&d3dpp, sizeof(d3dpp));
		d3dpp.Windowed = TRUE;
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	
		d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
		// d3dpp.hDeviceWindow = globalHWND;
		// release

		// reset device
		HRESULT result = d3ddev->Reset(&d3dpp);
		if( result != S_OK)
		{
			MessageBox(globalHWND, L"Error when become window", L"Error", MB_OK);
		}

		// Load resource again
		if(LoadGameResource() == 0)
			MessageBox(globalHWND, L"Error when become full screen", L"Error", MB_OK);

		// calculate middle screen
		int left = globalScreenWidth / 2  - SCREEN_WIDTH / 2;
		int top = globalScreenHeight / 2  - SCREEN_HEIGHT / 2;

		// set window position
		SetWindowPos(globalHWND, HWND_NOTOPMOST, left, top, SCREEN_WIDTH, SCREEN_HEIGHT, SWP_SHOWWINDOW);	
		
		//update window
		UpdateWindow(globalHWND);

		// now it is windowed
		isFullScreen = false;
	}
}

void MoveLeft(HWND hWnd)
{
	if(isFullScreen)
		return;
	// Get new client rectangle
	GetWindowRect(hWnd, &windowRect);
	
	SetWindowPos(hWnd, HWND_NOTOPMOST,windowRect.left - STEEP, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void MoveRight(HWND hWnd)
{
	if(isFullScreen)
		return;

	// Get new client rectangle
	GetWindowRect(hWnd, &windowRect);
	
	SetWindowPos(hWnd, HWND_NOTOPMOST,windowRect.left + STEEP, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}